/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.bpm.engine.impl.agenda;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.je.bpm.core.model.*;
import com.je.bpm.core.model.config.task.TaskFormBasicConfigImpl;
import com.je.bpm.core.model.event.BoundaryEvent;
import com.je.bpm.core.model.event.EndEvent;
import com.je.bpm.core.model.process.Process;
import com.je.bpm.core.model.process.SubProcess;
import com.je.bpm.core.model.task.KaiteBaseUserTask;
import com.je.bpm.core.model.task.KaiteCounterSignUserTask;
import com.je.bpm.core.model.task.KaiteMultiUserTask;
import com.je.bpm.core.model.task.KaiteTask;
import com.je.bpm.engine.ActivitiException;
import com.je.bpm.engine.delegate.ExecutionListener;
import com.je.bpm.engine.delegate.event.ActivitiEventType;
import com.je.bpm.engine.delegate.event.impl.ActivitiEventBuilder;
import com.je.bpm.engine.impl.bpmn.behavior.KaiteBaseUserTaskActivityBehavior;
import com.je.bpm.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import com.je.bpm.engine.impl.context.Context;
import com.je.bpm.engine.impl.delegate.ActivityBehavior;
import com.je.bpm.engine.impl.interceptor.CommandContext;
import com.je.bpm.engine.impl.persistence.entity.ExecutionEntity;
import com.je.bpm.engine.impl.persistence.entity.JobEntity;
import com.je.bpm.engine.impl.util.CollectionUtil;
import com.je.bpm.engine.impl.util.ProcessDefinitionUtil;
import com.je.bpm.engine.impl.util.ProcessInstanceHelper;
import com.je.bpm.engine.upcoming.UpcomingCommentInfoDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Operation that takes the current {@link FlowElement} set on the {@link ExecutionEntity}
 * and executes the associated {@link ActivityBehavior}. In the case of async, schedules a {@link com.je.bpm.engine.runtime.Job}.
 * <p>
 * Also makes sure the {@link ExecutionListener} instances are called.
 */
public class ContinueProcessOperation extends AbstractOperation {

    private static Logger logger = LoggerFactory.getLogger(ContinueProcessOperation.class);

    protected boolean forceSynchronousOperation;
    protected boolean inCompensation;

    public ContinueProcessOperation(CommandContext commandContext, ExecutionEntity execution, boolean forceSynchronousOperation, boolean inCompensation) {
        super(commandContext, execution);
        this.forceSynchronousOperation = forceSynchronousOperation;
        this.inCompensation = inCompensation;
    }

    public ContinueProcessOperation(CommandContext commandContext, ExecutionEntity execution) {
        this(commandContext, execution, false, false);
    }

    @Override
    public void run() {
        FlowElement currentFlowElement = getCurrentFlowElement(execution);
        if (currentFlowElement instanceof FlowNode) {
            continueThroughFlowNode((FlowNode) currentFlowElement);
        } else if (currentFlowElement instanceof SequenceFlow) {
            continueThroughSequenceFlow((SequenceFlow) currentFlowElement);
        } else {
            throw new ActivitiException("Programmatic error: no current flow element found or invalid type: " + currentFlowElement + ". Halting.");
        }
    }

    protected void executeProcessStartExecutionListeners() {
        Process process = ProcessDefinitionUtil.getProcess(execution.getProcessDefinitionId());
        executeExecutionListeners(process, execution.getParent(), ExecutionListener.EVENTNAME_START);
    }

    /**
     * 继续通过节点
     *
     * @param flowNode
     */
    protected void continueThroughFlowNode(FlowNode flowNode) {
        // Check if it's the initial flow element. If so, we must fire the execution listeners for the process too
        if (flowNode.getIncomingFlows() != null && flowNode.getIncomingFlows().size() == 0 && flowNode.getSubProcess() == null) {
            executeProcessStartExecutionListeners();
        }

        // For a subprocess, a new child execution is created that will visit the steps of the subprocess
        // The original execution that arrived here will wait until the subprocess is finished
        // and will then be used to continue the process instance.
        if (flowNode instanceof SubProcess) {
            createChildExecutionForSubProcess((SubProcess) flowNode);
        }
        if (flowNode instanceof KaiteTask || flowNode instanceof EndEvent) {
            Object upcomingInfoObj = Context.getCommandContext().getAttribute(KaiteBaseUserTaskActivityBehavior.UPCOMINGINFO);
            if (upcomingInfoObj != null && upcomingInfoObj instanceof UpcomingCommentInfoDTO) {
                UpcomingCommentInfoDTO upcomingCommentInfoDTO = (UpcomingCommentInfoDTO) upcomingInfoObj;
                ProcessInstanceHelper processInstanceHelper = commandContext.getProcessEngineConfiguration().getProcessInstanceHelper();
                TaskFormBasicConfigImpl taskFormBasicConfig = null;
                if (flowNode instanceof KaiteTask) {
                    KaiteBaseUserTask kaiteBaseUserTask = (KaiteBaseUserTask) flowNode;
                    taskFormBasicConfig = kaiteBaseUserTask.getTaskFormBasicConfig();
                }
                if (flowNode instanceof EndEvent) {
                    EndEvent endEvent = (EndEvent) flowNode;
                    taskFormBasicConfig = endEvent.getTaskFormBasicConfig();
                }
                Map<String, Object> bean = new HashMap<>();
                String beanId = upcomingCommentInfoDTO.getBeanId();
                BpmnModel bpmnModel = commandContext.getProcessEngineConfiguration().getRepositoryService().getBpmnModel(execution.getProcessDefinitionId());
                Map<String, String> map = taskFormBasicConfig.getTaskFormFieldSetValueConfig();
                for (String key : map.keySet()) {
                    bean.put(key, processInstanceHelper.getCustomerValue(commandContext, map.get(key), upcomingCommentInfoDTO.getComment()
                            , upcomingCommentInfoDTO.getSubmitType().getName()));
                }
                if (map.size() > 0) {
                    commandContext.updateBean(bean, beanId, bpmnModel.getMainProcess().getProcessConfig());
                }
            }
        }

        if (flowNode instanceof Activity && ((Activity) flowNode).hasMultiInstanceLoopCharacteristics()) {
            // the multi instance execution will look at async
            executeMultiInstanceSynchronous(flowNode);
        } else if (forceSynchronousOperation || !flowNode.isAsynchronous()) {
            executeSynchronous(flowNode);
        } else {
            executeAsynchronous(flowNode);
        }

    }

    protected void createChildExecutionForSubProcess(SubProcess subProcess) {
        ExecutionEntity parentScopeExecution = findFirstParentScopeExecution(execution);

        // Create the sub process execution that can be used to set variables
        // We create a new execution and delete the incoming one to have a proper scope that
        // does not conflict anything with any existing scopes

        ExecutionEntity subProcessExecution = commandContext.getExecutionEntityManager().createChildExecution(parentScopeExecution);
        subProcessExecution.setCurrentFlowElement(subProcess);
        subProcessExecution.setScope(true);

        commandContext.getExecutionEntityManager().deleteExecutionAndRelatedData(execution, null);
        execution = subProcessExecution;
    }

    protected void executeSynchronous(FlowNode flowNode) {
        commandContext.getHistoryManager().recordActivityStart(execution);

        // Execution listener: event 'start'
        if (CollectionUtil.isNotEmpty(flowNode.getExecutionListeners())) {
            executeExecutionListeners(flowNode, ExecutionListener.EVENTNAME_START);
        }

        // Execute any boundary events, sub process boundary events will be executed from the activity behavior
        if (!inCompensation && flowNode instanceof Activity) { // Only activities can have boundary events
            List<BoundaryEvent> boundaryEvents = ((Activity) flowNode).getBoundaryEvents();
            if (CollectionUtil.isNotEmpty(boundaryEvents)) {
                executeBoundaryEvents(boundaryEvents, execution);
            }
        }

        // Execute actual behavior
        // 执行真实的行为
        ActivityBehavior activityBehavior = (ActivityBehavior) flowNode.getBehavior();
        if (activityBehavior != null) {
            executeActivityBehavior(activityBehavior, flowNode);
        } else {
            logger.debug("No activityBehavior on activity '{}' with execution {}", flowNode.getId(), execution.getId());
            Context.getAgenda().planTakeOutgoingSequenceFlowsOperation(execution, true);
        }
    }

    protected void executeAsynchronous(FlowNode flowNode) {
        JobEntity job = commandContext.getJobManager().createAsyncJob(execution, flowNode.isExclusive());
        commandContext.getJobManager().scheduleAsyncJob(job);
    }

    protected void executeMultiInstanceSynchronous(FlowNode flowNode) {
        // Execution listener: event 'start'
        if (CollectionUtil.isNotEmpty(flowNode.getExecutionListeners())) {
            executeExecutionListeners(flowNode, ExecutionListener.EVENTNAME_START);
        }

        // Execute any boundary events, sub process boundary events will be executed from the activity behavior
        if (!inCompensation && flowNode instanceof Activity) { // Only activities can have boundary events
            List<BoundaryEvent> boundaryEvents = ((Activity) flowNode).getBoundaryEvents();
            if (CollectionUtil.isNotEmpty(boundaryEvents)) {
                executeBoundaryEvents(boundaryEvents, execution);
            }
        }

        // Execute the multi instance behavior
        //根据前端传递的参数进行设置顺序或者并须
        ActivityBehavior activityBehavior = (ActivityBehavior) flowNode.getBehavior();
//        activityBehavior = getActivityBehaviorBySequentials(activityBehavior, flowNode);
        if (activityBehavior != null) {
            executeActivityBehavior(activityBehavior, flowNode);
        } else {
            throw new ActivitiException("Expected an activity behavior in flow node " + flowNode.getId());
        }
    }


    private ActivityBehavior getActivityBehaviorBySequentials(ActivityBehavior activityBehavior, FlowNode flowNode) {
        if (flowNode instanceof KaiteMultiUserTask || flowNode instanceof KaiteCounterSignUserTask) {
            Object sequentialsObject = execution.getTransientVariable(KaiteBaseUserTaskActivityBehavior.SEQUENTIALS);
            if (sequentialsObject != null) {
                String sequentialsStr = (String) sequentialsObject;
                JSONArray sequentialsJsonArray = JSONArray.parseArray(sequentialsStr);
                for (Object nodeSequentConfig : sequentialsJsonArray) {
                    if (nodeSequentConfig != null) {
                        JSONObject jsonObject = JSONObject.parseObject(nodeSequentConfig.toString());
                        String nodeId = (String) jsonObject.get("nodeId");
                        if (nodeId.equals(flowNode.getId())) {
                            String sequential = (String) jsonObject.get("sequential");
                            KaiteBaseUserTask kaiteBaseUserTask = (KaiteBaseUserTask) flowNode;
                            if (flowNode instanceof KaiteMultiUserTask) {
                                return commandContext.getProcessEngineConfiguration().getBpmnParser().getActivityBehaviorFactory()
                                        .createKaiteMultiUserTaskActivityBehavior((KaiteMultiUserTask) kaiteBaseUserTask);
                            }
                            if (flowNode instanceof KaiteCounterSignUserTask) {
                                return commandContext.getProcessEngineConfiguration().getBpmnParser().getActivityBehaviorFactory()
                                        .createKaiteMultiUserTaskActivityBehavior((KaiteMultiUserTask) kaiteBaseUserTask);
                            }
                        }
//                                [{nodeId:"countersign9kCbK1B3IVPDDtQPy23",sequential:"0"}]
                    }
                }
                execution.getProcessInstance().setTransientVariable(KaiteBaseUserTaskActivityBehavior.SEQUENTIALS, "");
            }
        }
        return activityBehavior;
    }

    protected void executeActivityBehavior(ActivityBehavior activityBehavior, FlowNode flowNode) {
        logger.debug("Executing activityBehavior {} on activity '{}' with execution {}", activityBehavior.getClass(), flowNode.getId(), execution.getId());
        if (Context.getProcessEngineConfiguration() != null && Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled() &&
                !(activityBehavior instanceof MultiInstanceActivityBehavior)) {
            Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
                    ActivitiEventBuilder.createActivityEvent(ActivitiEventType.ACTIVITY_STARTED,
                            flowNode.getId(),
                            flowNode.getName(),
                            execution.getId(),
                            execution.getProcessInstanceId(),
                            execution.getProcessDefinitionId(),
                            flowNode));
        }
        try {
            activityBehavior.execute(execution);
        } catch (RuntimeException e) {
            throw e;
        }
    }

    /**
     * 继续通过顺序流
     *
     * @param sequenceFlow
     */
    protected void continueThroughSequenceFlow(SequenceFlow sequenceFlow) {
        // Execution listener. Sequenceflow only 'take' makes sense ... but we've supported all three since the beginning
        if (CollectionUtil.isNotEmpty(sequenceFlow.getExecutionListeners())) {
            executeExecutionListeners(sequenceFlow, ExecutionListener.EVENTNAME_START);
            executeExecutionListeners(sequenceFlow, ExecutionListener.EVENTNAME_TAKE);
            executeExecutionListeners(sequenceFlow, ExecutionListener.EVENTNAME_END);
        }

        // Firing event that transition is being taken
        if (Context.getProcessEngineConfiguration() != null && Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {
            FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
            FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
            Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(
                    ActivitiEventBuilder.createSequenceFlowTakenEvent(
                            (ExecutionEntity) execution,
                            ActivitiEventType.SEQUENCEFLOW_TAKEN,
                            sequenceFlow.getId(),
                            sourceFlowElement != null ? sourceFlowElement.getId() : null,
                            sourceFlowElement != null ? (String) sourceFlowElement.getName() : null,
                            sourceFlowElement != null ? sourceFlowElement.getClass().getName() : null,
                            sourceFlowElement != null ? ((FlowNode) sourceFlowElement).getBehavior() : null,
                            targetFlowElement != null ? targetFlowElement.getId() : null,
                            targetFlowElement != null ? targetFlowElement.getName() : null,
                            targetFlowElement != null ? targetFlowElement.getClass().getName() : null,
                            targetFlowElement != null ? ((FlowNode) targetFlowElement).getBehavior() : null));
        }

        FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement();
        execution.setCurrentFlowElement(targetFlowElement);

        logger.debug("Sequence flow '{}' encountered. Continuing process by following it using execution {}", sequenceFlow.getId(), execution.getId());
        Context.getAgenda().planContinueProcessOperation(execution);
    }

    protected void executeBoundaryEvents(Collection<BoundaryEvent> boundaryEvents, ExecutionEntity execution) {
        // The parent execution becomes a scope, and a child execution is created for each of the boundary events
        for (BoundaryEvent boundaryEvent : boundaryEvents) {
            if (CollectionUtil.isEmpty(boundaryEvent.getEventDefinitions()) || (boundaryEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition)) {
                continue;
            }

            // A Child execution of the current execution is created to represent the boundary event being active
            ExecutionEntity childExecutionEntity = commandContext.getExecutionEntityManager().createChildExecution((ExecutionEntity) execution);
            childExecutionEntity.setParentId(execution.getId());
            childExecutionEntity.setCurrentFlowElement(boundaryEvent);
            childExecutionEntity.setScope(false);

            ActivityBehavior boundaryEventBehavior = ((ActivityBehavior) boundaryEvent.getBehavior());
            logger.debug("Executing boundary event activityBehavior {} with execution {}", boundaryEventBehavior.getClass(), childExecutionEntity.getId());
            boundaryEventBehavior.execute(childExecutionEntity);
        }
    }
}
